#ifndef RAIS_TYPELIST_H
#define RAIS_TYPELIST_H

#include <Rais_Meta.h>

namespace rais{
	
inline namespace meta{



template <typename...>
struct TypeNode {};

template <typename This, typename... Nexts>
struct TypeNode<This, Nexts...> {
	using type = This;
	using next = TypeNode<Nexts...>;
};

template <typename...> 
class TypeList;


//universial list functions
	
template <typename List>
struct Nodelize {};
template <template <typename...> class ListTempl, typename... Ts>
struct Nodelize<ListTempl<Ts...>> {
	using result = TypeNode<Ts...>;
};
template <typename List>
using nodelize = typename Nodelize<List>::result;


template <typename...>
struct StopTraverse {};
template <typename... States>
using stop_traverse = StopTraverse<States...>;

template <template <typename, typename...> class F, typename Node, typename StatesPack>
struct TraverseImpl_ExpandingPack;

template <template <typename, typename...> class F, typename Node, typename... States>
struct TraverseImpl {
	using result = typename TraverseImpl_ExpandingPack<F, typename Node::next, F<typename Node::type, States...>>::result;
};
template <template <typename, typename...> class F, template <typename...> class NodeTempl, typename... States>
struct TraverseImpl<F, NodeTempl<>, States...> {
	using result = typename TraverseImpl_ExpandingPack<F, NodeTempl<>, States...>::result;
};

template <template <typename, typename...> class F, typename Node, typename StatesPack>
struct TraverseImpl_ExpandingPack {};
template <template <typename, typename...> class F, typename Node, template <typename...> class StatesPackTempl, typename... States>
struct TraverseImpl_ExpandingPack<F, Node, StatesPackTempl<States...>> {
	using result = typename meta_if<is_same_templ<stop_traverse, StatesPackTempl>::value, StatesPackTempl<States...>, typename TraverseImpl<F, Node, States...>::result>;
};
template <template <typename, typename...> class F, template <typename...> class NodeTempl, template <typename...> class StatesPackTempl, typename... States>
struct TraverseImpl_ExpandingPack<F, NodeTempl<>, StatesPackTempl<States...>> {
	using result = StatesPackTempl<States...>;
};

template <typename List, template <typename, typename...> class F, typename... InitStates>
struct Traverse {
	using result = typename TraverseImpl<F, nodelize<List>, InitStates...>::result;
};

//A meta function for traversing a list, requires a meta function 'F' that accept a current traversing type and some traversing states.
template <typename List, template <typename, typename...> class F, typename... InitStates>
using traverse = typename Traverse<List, F, InitStates...>::result;


template <typename List, typename Index>
struct Get {
	template <typename T, typename CurrentIndex, typename TargetIndex>
	using helper = TypeNode<typename CurrentIndex::inc, TargetIndex>;
	
};

template <typename... Types>
class TypeList {
	using head = TypeNode<Types...>;
	using self = TypeList<Types...>;
	
	public:
	
	using length_t = decltype(sizeof...(Types));
	
	
	static constexpr length_t length = sizeof...(Types);
	
	//meta function 'get'
	template <length_t Target_idx, length_t This_idx = 0, typename This_node = head>
	struct Get {
		static_assert(Target_idx < length, "target index is out of bound.");
		using result = typename Get<Target_idx, This_idx + 1, typename This_node::next>::result;
	};
	template <length_t Target_idx, typename This_node>
	struct Get<Target_idx, Target_idx, This_node> {
		using result = typename This_node::type;
	};
	template <length_t Target_idx>
	using get = typename Get<Target_idx>::result;

	//meta function 'push'
	template <typename New_type>
	struct Push {
		using result = TypeList<Types..., New_type>;
	};
	template <typename New_type>
	using push = typename Push<New_type>::result;
	
	//meta function 'unshift'
	template <typename New_type>
	struct Unshift {
		using result = TypeList<New_type, Types...>;
	};
	template <typename New_type>
	using unshift = typename Unshift<New_type>::result;
	
	template <typename T, typename... Ts>
	struct ShiftImpl {
		using result = TypeList<Ts...>;
		using type = T;
	};

	//meta function 'shift'
	struct Shift {
		using result = typename ShiftImpl<self>::result;
		using type = typename ShiftImpl<self>::type;
	};
	using shift = typename Shift::result;

	
	//meta function 'pop'
	struct Pop {
		
	};
	// using pop = typename Pop::result;


	
	
	//to avoid some confusing bugs
	template <typename List1, typename List2>
	struct NonMemberConcat {};
	template <typename... New_types1, typename... New_types2>
	struct NonMemberConcat<TypeList<New_types1...>, TypeList<New_types2...>> {
		using result = TypeList<New_types1..., New_types2...>;
	};
	
	template <typename List, typename... Lists>
	struct ConcatImpl {
		using result = typename NonMemberConcat<List, typename ConcatImpl<Lists...>::result>::result;
	};
	template <typename List1, typename List2>
	struct ConcatImpl<List1, List2> {
		using result = typename NonMemberConcat<List1, List2>::result;
	};
	
	//meta function 'concat'
	template <typename... Lists>
	struct Concat {
		using result = typename ConcatImpl<self, Lists...>::result;
	};
	
	template <typename... Lists>
	using concat = typename Concat<Lists...>::result;
	
	//other functions:
	
	//meta function 'map'
	template <template <typename...> class Function>
	struct Map {
		using result = TypeList<Function<Types>...>;
	};
	template <template <typename...> class Function>
	using map = typename Map<Function>::result;
	
	//meta function 'contain'
	
	// template <typename Checked_type, typename This_type = typename head::type, typename This_node = head>
	// struct Contain {
		// using result = typename Contain<Checked_type, typename This_node::next::type, typename This_node::next>::result;
	// };
	// template <typename Checked_type, typename This_node>
	// struct Contain<Checked_type, Checked_type, This_node> {
		// using result = meta_bool<true>;
	// };
	// template <typename Checked_type>
	// struct Contain<Checked_type, Checked_type, TypeNode<Checked_type>> {
		// using result = meta_bool<true>;
	// };
	// template <typename Checked_type, typename This_type>
	// struct Contain<Checked_type, This_type, TypeNode<This_type>> {
		// using result = meta_bool<false>;
	// };
	// template <typename Checked_type>
	// using contain = typename Contain<Checked_type>::result;
	
	//meta function 'cast'
	template <template <typename...> class Container>
	struct Cast {
		using result = Container<Types...>;
	};
	template <template <typename...> class Container>
	using cast = typename Cast<Container>::result;
	
	
	//meta function 'ToArray', require 'GetData::data' interface.
	template <typename Array_type>
	struct ToArrayImpl {
		static constexpr Array_type result[sizeof...(Types)] = {Types::GetData::data...};
	};
	template <typename Array_type>	
	struct ToArray {
	};
	
	
	template <template <typename, typename...> class F, typename CurrentNode, typename CurrentStatesPack>
	struct ForEachExpandPack;
	
	template <template <typename, typename...> class F, typename CurrentNode, typename... CurrentStates>
	struct ForEachImpl {
		using result = typename ForEachExpandPack<F, CurrentNode, typename F<typename CurrentNode::type, CurrentStates...>::result>::result;
	};

	template <template <typename, typename...> class F, typename CurrentNode, typename CurrentStatesPack>
	struct ForEachExpandPack {};
	template <template <typename, typename...> class F, typename CurrentNode, template <typename...> class CurrentStatesPackTempl, typename... CurrentStates>
	struct ForEachExpandPack<F, CurrentNode, CurrentStatesPackTempl<CurrentStates...>> {
		using result = typename ForEachImpl<F, typename CurrentNode::next, CurrentStates...>::result;
	};
	template <template <typename, typename...> class F, template <typename...> class CurrentNodeTempl, typename CurrentStatesPack> 
	struct ForEachExpandPack<F, CurrentNodeTempl<>, CurrentStatesPack> {
		using result = CurrentStatesPack;
	};
	
	template <template <typename, typename...> class F, typename... InitStates>
	struct ForEach {
		using result = typename ForEachImpl<F, head, InitStates...>::result;
	};
	
};

namespace predefined_list {
	
	
	// using uint_list    = TypeList<uint8_t, uint16_t, uint32_t, uint64_t>;
	// using int_list     = TypeList<int8_t, int16_t, int32_t, int64_t>;
}


}	//namespace meta

}	//namespace rais

#endif
